/*
  This file defines some basic smoke tests for cross compilation.
  Individual jobs can be tested by running:

  $ nix-build pkgs/top-level/release-cross.nix -A <jobname>.<package> --arg supportedSystems '[builtins.currentSystem]'

  e.g.

  $ nix-build pkgs/top-level/release-cross.nix -A crossMingw32.nix --arg supportedSystems '[builtins.currentSystem]'

  To build all of the bootstrapFiles bundles on every enabled platform, use:

  $ nix-build --expr 'with import ./pkgs/top-level/release-cross.nix {supportedSystems = [builtins.currentSystem];}; builtins.mapAttrs (k: v: v.build) bootstrapTools'
*/

{
  # The platforms *from* which we cross compile.
  supportedSystems ? [
    "x86_64-linux"
    "aarch64-linux"
    "aarch64-darwin"
  ],
  # Strip most of attributes when evaluating to spare memory usage
  scrubJobs ? true,
  # Attributes passed to nixpkgs. Don't build packages marked as unfree.
  nixpkgsArgs ? {
    config = {
      allowAliases = false;
      allowUnfree = false;
      inHydra = true;
    };
    __allowFileset = false;
  },
}:

let
  release-lib = import ./release-lib.nix {
    inherit supportedSystems scrubJobs nixpkgsArgs;
  };

  inherit (release-lib)
    all
    assertTrue
    darwin
    forMatchingSystems
    hydraJob'
    linux
    mapTestOnCross
    pkgsForCross
    unix
    ;

  inherit (release-lib.lib)
    mapAttrs
    addMetaAttrs
    elem
    getAttrFromPath
    isDerivation
    maintainers
    mapAttrsRecursive
    mapAttrsRecursiveCond
    recursiveUpdate
    systems
    ;

  inherit (release-lib.lib.attrsets)
    removeAttrs
    ;

  nativePlatforms = all;

  embedded = {
    buildPackages.binutils = nativePlatforms;
    buildPackages.gcc = nativePlatforms;
    libc = nativePlatforms;
  };

  common = {
    buildPackages.binutils = nativePlatforms;
    gmp = nativePlatforms;
    libc = nativePlatforms;
    nix = nativePlatforms;
    nixVersions.git = nativePlatforms;
    mesa = nativePlatforms;
    rustc = nativePlatforms;
    cargo = nativePlatforms;
  };

  gnuCommon = recursiveUpdate common {
    buildPackages.gcc = nativePlatforms;
    coreutils = nativePlatforms;
    haskell.packages.ghcHEAD.hello = nativePlatforms;
    haskellPackages.hello = nativePlatforms;
  };

  linuxCommon = recursiveUpdate gnuCommon {
    buildPackages.gdb = nativePlatforms;

    bison = nativePlatforms;
    busybox = nativePlatforms;
    dropbear = nativePlatforms;
    ed = nativePlatforms;
    ncurses = nativePlatforms;
    patch = nativePlatforms;
  };

  windowsCommon = recursiveUpdate gnuCommon {
    boehmgc = nativePlatforms;
    libffi = nativePlatforms;
    libtool = nativePlatforms;
    libunistring = nativePlatforms;
    windows.pthreads = nativePlatforms;
  };

  cygwinCommon = {
    hello = nativePlatforms;
  };

  wasiCommon = {
    gmp = nativePlatforms;
    boehmgc = nativePlatforms;
    hello = nativePlatforms;
    zlib = nativePlatforms;
  };

  darwinCommon = {
    buildPackages.binutils = darwin;
  };

  rpiCommon = linuxCommon // {
    vim = nativePlatforms;
    unzip = nativePlatforms;
    ddrescue = nativePlatforms;
    lynx = nativePlatforms;
    patchelf = nativePlatforms;
    buildPackages.binutils = nativePlatforms;
    mpg123 = nativePlatforms;
  };

  # Enabled-but-unsupported platforms for which nix is known to build.
  # We provide Hydra-built `nixStatic` for these platforms.  This
  # allows users to bootstrap their own system without either (a)
  # trusting binaries from a non-Hydra source or (b) having to fight
  # with their host distribution's versions of nix's numerous
  # build dependencies.
  nixCrossStatic = {
    nixStatic = linux; # no need for buildPlatform=*-darwin
  };

in

{
  # These derivations from a cross package set's `buildPackages` should be
  # identical to their vanilla equivalents --- none of these package should
  # observe the target platform which is the only difference between those
  # package sets.
  ensureUnaffected =
    let
      # Absurd values are fine here, as we are not building anything. In fact,
      # there probably a good idea to try to be "more parametric" --- i.e. avoid
      # any special casing.
      crossSystem = {
        config = "mips64el-apple-windows-gnu";
        libc = "glibc";
      };

      # Converting to a string (drv path) before checking equality is probably a
      # good idea lest there be some irrelevant pass-through debug attrs that
      # cause false negatives.
      testEqualOne =
        path: system:
        let
          f =
            path: crossSystem: system:
            toString (getAttrFromPath path (pkgsForCross crossSystem system));
        in
        assertTrue (f path null system == f ([ "buildPackages" ] ++ path) crossSystem system);

      testEqual = path: systems: forMatchingSystems systems (testEqualOne path);

      mapTestEqual = mapAttrsRecursive testEqual;

    in
    mapTestEqual {
      boehmgc = nativePlatforms;
      libffi = nativePlatforms;
      libiconv = nativePlatforms;
      libtool = nativePlatforms;
      zlib = nativePlatforms;
      readline = nativePlatforms;
      libxml2 = nativePlatforms;
      guile = nativePlatforms;
    };

  crossIphone64 = mapTestOnCross systems.examples.iphone64 darwinCommon;

  crossIphone32 = mapTestOnCross systems.examples.iphone32 darwinCommon;

  # Test some cross builds to the Sheevaplug
  crossSheevaplugLinux = mapTestOnCross systems.examples.sheevaplug (
    linuxCommon
    // {
      ubootSheevaplug = nativePlatforms;
    }
  );

  # Test some cross builds on 32 bit mingw-w64
  crossMingw32 = mapTestOnCross systems.examples.mingw32 windowsCommon;

  # Test some cross builds on 64 bit mingw-w64
  crossMingwW64 = mapTestOnCross systems.examples.mingwW64 windowsCommon;

  x86_64-cygwin = mapTestOnCross systems.examples.x86_64-cygwin cygwinCommon;

  # Linux on mipsel
  fuloongminipc = mapTestOnCross systems.examples.fuloongminipc linuxCommon;
  ben-nanonote = mapTestOnCross systems.examples.ben-nanonote linuxCommon;

  # Javascript
  ghcjs = mapTestOnCross systems.examples.ghcjs {
    haskell.packages.native-bignum.ghcHEAD.hello = nativePlatforms;
    haskellPackages.hello = nativePlatforms;
  };

  # Linux on Raspberrypi
  rpi = mapTestOnCross systems.examples.raspberryPi rpiCommon;
  rpi-musl = mapTestOnCross systems.examples.muslpi rpiCommon;

  # Linux on the Remarkable
  remarkable1 = mapTestOnCross systems.examples.remarkable1 linuxCommon;
  remarkable2 = mapTestOnCross systems.examples.remarkable2 linuxCommon;

  # Linux on armv7l-hf
  armv7l-hf = mapTestOnCross systems.examples.armv7l-hf-multiplatform linuxCommon;

  pogoplug4 = mapTestOnCross systems.examples.pogoplug4 linuxCommon;

  # Linux on aarch64
  aarch64 = mapTestOnCross systems.examples.aarch64-multiplatform linuxCommon;
  aarch64-musl = mapTestOnCross systems.examples.aarch64-multiplatform-musl linuxCommon;

  # Linux on RISCV
  riscv64 = mapTestOnCross systems.examples.riscv64 linuxCommon;
  riscv32 = mapTestOnCross systems.examples.riscv32 linuxCommon;

  # Linux on LoongArch
  loongarch64-linux = mapTestOnCross systems.examples.loongarch64-linux linuxCommon;

  m68k = mapTestOnCross systems.examples.m68k linuxCommon;
  s390x = mapTestOnCross systems.examples.s390x linuxCommon;

  # (Cross-compiled) Linux on x86
  x86_64-musl = mapTestOnCross systems.examples.musl64 linuxCommon;
  x86_64-gnu = mapTestOnCross systems.examples.gnu64 linuxCommon;
  i686-musl = mapTestOnCross systems.examples.musl32 linuxCommon;
  i686-gnu = mapTestOnCross systems.examples.gnu32 linuxCommon;

  ppc64le = mapTestOnCross systems.examples.powernv linuxCommon;
  ppc64le-musl = mapTestOnCross systems.examples.musl-power linuxCommon;

  android64 = mapTestOnCross systems.examples.aarch64-android-prebuilt linuxCommon;
  android32 = mapTestOnCross systems.examples.armv7a-android-prebuilt linuxCommon;

  wasi32 = mapTestOnCross systems.examples.wasi32 wasiCommon;

  msp430 = mapTestOnCross systems.examples.msp430 embedded;
  mmix = mapTestOnCross systems.examples.mmix embedded;
  vc4 = mapTestOnCross systems.examples.vc4 embedded;
  or1k = mapTestOnCross systems.examples.or1k embedded;
  avr = mapTestOnCross systems.examples.avr embedded;
  arm-embedded = mapTestOnCross systems.examples.arm-embedded embedded;
  arm-embedded-nano = mapTestOnCross systems.examples.arm-embedded-nano embedded;
  armhf-embedded = mapTestOnCross systems.examples.armhf-embedded embedded;
  aarch64-embedded = mapTestOnCross systems.examples.aarch64-embedded embedded;
  aarch64be-embedded = mapTestOnCross systems.examples.aarch64be-embedded embedded;
  powerpc-embedded = mapTestOnCross systems.examples.ppc-embedded embedded;
  powerpcle-embedded = mapTestOnCross systems.examples.ppcle-embedded embedded;
  i686-embedded = mapTestOnCross systems.examples.i686-embedded embedded;
  x86_64-embedded = mapTestOnCross systems.examples.x86_64-embedded embedded;
  riscv64-embedded = mapTestOnCross systems.examples.riscv64-embedded embedded;
  riscv32-embedded = mapTestOnCross systems.examples.riscv32-embedded embedded;
  rx-embedded = mapTestOnCross systems.examples.rx-embedded embedded;

  x86_64-freebsd = mapTestOnCross systems.examples.x86_64-freebsd common;
  x86_64-netbsd = mapTestOnCross systems.examples.x86_64-netbsd common;
  x86_64-openbsd = mapTestOnCross systems.examples.x86_64-openbsd common;

  # we test `embedded` instead of `linuxCommon` because very few packages
  # successfully cross-compile to Redox so far
  x86_64-redox = mapTestOnCross systems.examples.x86_64-unknown-redox embedded;

  # Cross-built bootstrap tools for every supported platform
  bootstrapTools =
    let
      linuxTools = import ../stdenv/linux/make-bootstrap-tools-cross.nix { system = "x86_64-linux"; };
      freebsdTools = import ../stdenv/freebsd/make-bootstrap-tools-cross.nix { system = "x86_64-linux"; };
      linuxMeta = {
        maintainers = [ maintainers.dezgeg ];
      };
      freebsdMeta = {
        maintainers = [ maintainers.rhelmot ];
      };
      mkBootstrapToolsJob =
        meta: drv:
        assert elem drv.system supportedSystems;
        hydraJob' (addMetaAttrs meta drv);
      linux =
        mapAttrsRecursiveCond (as: !isDerivation as) (name: mkBootstrapToolsJob linuxMeta)
          # The `bootstrapTools.${platform}.bootstrapTools` derivation
          # *unpacks* the bootstrap-files using their own `busybox` binary,
          # so it will fail unless buildPlatform.canExecute hostPlatform.
          # Unfortunately `bootstrapTools` also clobbers its own `system`
          # attribute, so there is no way to detect this -- we must add it
          # as a special case.  We filter the "test" attribute (only from
          # *cross*-built bootstrapTools) for the same reason.
          (
            mapAttrs (
              _: v:
              removeAttrs v [
                "bootstrapTools"
                "test"
              ]
            ) linuxTools
          );
      freebsd = mapAttrsRecursiveCond (as: !isDerivation as) (
        name: mkBootstrapToolsJob freebsdMeta
      ) freebsdTools;
    in
    linux // freebsd;

  # Cross-built nixStatic for platforms for enabled-but-unsupported platforms
  mips64el-nixCrossStatic = mapTestOnCross systems.examples.mips64el-linux-gnuabi64 nixCrossStatic;
  powerpc64le-nixCrossStatic = mapTestOnCross systems.examples.powernv nixCrossStatic;
}
